import lodash from "lodash";
import eu from "../js/enum";
import {parseInt} from "lodash/string";
import pd, {split} from "./pandas";
import Util from "@/components/js/util";


let app = wps.EtApplication();

function openOfficeFileFromSystemDemo(param) {
    let jsonObj = typeof param == "string" ? JSON.parse(param) : param;
    alert("从业务系统传过来的参数为：" + JSON.stringify(jsonObj));
    return {wps加载项项返回: jsonObj.filepath + ", 这个地址给的不正确"};
}

function InvokeFromSystemDemo(param) {
    let jsonObj = typeof param == "string" ? JSON.parse(param) : param;
    let handleInfo = jsonObj.Index;
    switch (handleInfo) {
        case "getDocumentName": {
            let docName = "";
            if (app.ActiveWorkbook) {
                docName = app.ActiveWorkbook.Name;
            }

            return {当前打开的文件名为: docName};
        }

        case "newDocument": {
            let newDocName = "";
            let doc = app.Workbooks.Add();
            newDocName = doc.Name;

            return {操作结果: "新建文档成功，文档名为：" + newDocName};
        }

        case "OpenFile": {
            let filePath = jsonObj.filepath;
            app.Workbooks.OpenFromUrl(filePath);
            return {操作结果: "打开文件成功"};
        }
    }

    return {其它xxx: ""};
}

function ColumnChar2Num(y) {
    /**
     * 将Excel中以字母表示的列名转换为数字，因为程序中只能识别以数字排序的列，如A表示第1列
     * @type {string}
     */
    y = y.trim();
    y = y.toUpperCase();
    if (y.length === 1) {
        y = y.charCodeAt() - 64; // 'A'的编码是65，'A'列即第1列，excel中从(1,1)开始，没有(0,0)单元格。
    } else if (y.length === 2) {
        let _ = y[0];
        _ = (_.charCodeAt() - 64) * 26;
        y = y[1].charCodeAt() - 64 + _;
    } else {
        alert("列索引超范围，目前只支持A-ZZ之间的列索引，出错列索引为：" + y);
    }
    return y;
}

export function Num2ColumnChar(y) {
    /**
     * 将数字索引转换为Excel的列明，如第1列返回'A'
     */

    if (typeof y === "string") {
        return y;
    }
    let res = "";
    let x = Math.floor(y / 26);
    if (x > 0) {
        res = String.fromCharCode(x + 64);
    }
    x = y % 26;
    res = res + String.fromCharCode(x + 64);
    return res;
}

export function get_cell_value_by_pos(pos) {
    /**
     * 根据位置字符串表示的单元的值，如pos="原始数据!E20", pos="E20"等
     * @private
     */
    let _ = pos.split("!")
    let sheet_name = null;
    if (lodash.isArray(_) && _.length === 1) {
        sheet_name = null;
    } else {
        sheet_name = _[0];
    }
    let row = get_row_from_pos_string(pos)
    let col = get_col_from_pos_string(pos)
    let res = get_cell_value(row, col, sheet_name)
    return res
}

export function get_cell_value(row, col, sheet_name = undefined, return_cell = false) {
    /**
     * 获取ET表格中（x,y）单元格的值，excel中的单元格下表从（1,1）开始，即A列的列编号为1，不是0
     * 返回值是字符串，如果为null，则转换为空字符串""返回，示例：
     * get_cell_value(2, 'B')  // 获得当前活动数据表第B2单元格的内容
     */
    let cells;
    if (sheet_name) {
        // 如果指定了sheet名，则获取指定sheet上的数据
        try {
            cells = app.Worksheets.Item(sheet_name).Cells;
        } catch (TypeError) {
            console.log("获取单元格(" + row + ", " + col + ")内容失败，sheet_name = " + sheet_name);
            return "";
        }
    } else {
        // 否则使用当前激活sheet上的数据
        cells = app.Cells;
    }
    if (typeof col === "string") {
        col = ColumnChar2Num(col);
    }
    let res = cells.Item(row, col);
    if (!return_cell) { // 如果需要的是值，则进一步取值
        res = res.Value2
    }
    if (res === null) {
        res = ""
    }
    return res;
}

function set_cell_value(x, y, value, sheet_name) {
    /**
     * 设置ET表格中（x,y）单元格的值
     * 示例：
     * set_cell_value(2, 'B', 12)  // 将当前数据表中B2单元格的内容设置为12
     */
    let cells;
    if (sheet_name) {
        // 如果指定了sheet名，则获取指定sheet上的数据
        cells = app.Worksheets.Item(sheet_name).Cells;
    } else {
        cells = app.Cells;
    }

    if (typeof y === "string") {
        y = ColumnChar2Num(y);
    }
    cells.Item(x, y).Value2 = value;
}

function set_cell_formula(x, y, formula, sheet_name) {
    /**
     * 设置ET表格中（x,y）单元格的公式，示例：
     * set_cell_formula(2, "B", "=B1*10") // 将当前数据表中B2单元格的公式设置为"=B1*10")
     */
    let cells;
    if (sheet_name) {
        // 如果指定了sheet名，则获取指定sheet上的数据
        cells = get_sheet_by_name(sheet_name).Cells;
    } else {
        cells = app.Cells;
    }

    if (typeof y === "string") {
        y = ColumnChar2Num(y);
    }
    cells.Item(x, y).Formula = formula;
}


export function RGB(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
}

export function set_cell_color(x, y, bg_color, fg_color) {
    /**
     * 设置单元格前景色和背景色.
     * ColorIndex = 3  是红色，4是绿色，5是蓝色，6是黄色，颜色可用WPS中的JS调试器测试
     * ColorIndex = (255,0,0)是红色
     * @param bg_color: 背景颜色，可取值：3,4,5,6或(255,0,0)等
     * @param fg_color: 前景色，可取值：3,4,5,6或(255,0,0)等
     */
    if (bg_color) {
        if (typeof (bg_color) === "object") { // 如果bg_color是RGB类型
            get_cell_object(x, y).Interior.ColorIndex = (bg_color.r, bg_color.g, bg_color.b);
        } else {
            get_cell_object(x, y).Interior.ColorIndex = bg_color;
        }
    }
    if (fg_color) {
        if (typeof (bg_color) === "object") {
            get_cell_object(x, y).Font.ColorIndex = (bg_color.r, bg_color.g, bg_color.b);
        } else {
            get_cell_object(x, y).Font.ColorIndex = bg_color;
        }
    }
}

export function set_cells(x, y, data) {
    /**
     * 将 二维列表data 写入（x,y）单元格开始的区域
     */
    for (let i = 0; i < data.length; i++) {
        for (let j = 0; j < data[i].length; j++) {
            set_cell_value(x + i, y + j, data[i][j]);
        }
    }
}

export function delete_row(row_index, sheet_name) {
    /**
     * 删除工作表中的行
     */
    if (!sheet_name) {
        sheet_name = app.ActiveSheet.Name
    }
    let sheet = get_sheet_by_name(sheet_name);
    let row = sheet.Rows.Item(row_index);
    row.Delete();
}

export function delete_column(col_index, sheet_name) {
    /**
     * 删除工作表中的列
     */
    if (!sheet_name) {
        sheet_name = app.ActiveSheet.Name
    }
    let sheet = get_sheet_by_name(sheet_name);
    let column = sheet.Columns.Item(col_index);
    column.Delete();
}

export function get_active_sheet_name() {
    /**
     * 获取当前激活的工作表的名称
     */
    return app.ActiveSheet.Name.toString()
}

export function get_sheet_by_name(name) {
    /**
     * 获取给定名称的工作表对象，返回的是Sheet对象，如果name不存在，则返回空
     */
    let res = Object(app.Worksheets.Item(name));
    if (res.Cells !== undefined) {
        // 说明是一个object，即获得了对应的工作表
        return Object(app.Worksheets.Item(name));
    } else {
        // 此时，res={}，其具有长度属性，将其置空，表示没有获取到对象
        return null;
    }
}

function get_cell_object(x, y) {
    return Object(app.Cells.Item(x, y));
}

function create_new_sheet(sheet_name) {
    /**
     * 创建新表，默认在最后一个位置创建工作表
     * @type {Et.EtSheets}
     */
    let sheets = app.Worksheets;
    if (!get_sheet_by_name(sheet_name)) {
        let newSheet = app.Worksheets.Add(sheets.Item(sheets.Count));
        newSheet.Name = sheet_name;
    }
}

function add_title_region(sheet_name) {
    /**
     * 添加数据计算表的表头，表头包括"项目", "开始日期", "开始时间", "结束时间"
     * @type {any}
     */
    let cells = get_sheet_by_name(sheet_name).Cells;

    let row = 1;
    activate_sheet(sheet_name);
    let cols = getLastColumnIndexOfSheet("数据整理")
    for (let i = 2; i <= cols; i++) {
        let y_char = Num2ColumnChar(i);
        cells.Item(row, i).Formula = form_formula2("数据整理!{row}", y_char, row);
        cells.Item(row + 1, i).Formula = form_formula2("数据整理!{row+1}", y_char, row);
        cells.Item(row + 2, i).Formula = form_formula2("数据整理!{row+2}", y_char, row);
        cells.Item(row + 3, i).Formula = form_formula2("数据整理!{row+3}", y_char, row);
        if (i > 3) {
            cells.Item(row + 1, i).NumberFormat = "yyyy-m-d";
            cells.Item(row + 2, i).NumberFormat = "hh:mm";
            cells.Item(row + 3, i).NumberFormat = "hh:mm";
        }
    }
}

function createTurbineSheet(sheet_name) {
    let sheet;
    if (sheet_name === undefined) {
        sheet_name = "汽机";
    }
    let sheets = app.Worksheets;
    sheet = get_sheet_by_name(sheet_name);

    if (get_sheet_by_name("数据整理")) {
        // 如果当前工作簿中存在“数据整理”工作表
        if (!sheet) {
            // 如果不存在sheet_name代表的工作表，则创建
            let newSheet = app.Worksheets.Add(undefined, sheets.Item("数据整理")); // 在数据整理之后添加新sheet
            newSheet.Name = sheet_name;
            // 新创建的"汽机"工作表需要添加表头
            add_title_region("汽机");
        } else {
            console.log("已经存在名为" + sheet_name + "的工作表！");
        }
    } else {
        alert("未找到“数据整理”工作表，因此无法获得计算所需的数据源，无法计算汽轮机性能，请检查！");
        return false;
    }
    return true;
}

export function self_debug(说明, obj) {
    console.log(说明);
    console.log(obj);
}

export function get_row_from_pos_string(pos_string) {
    /**
     * 从形如"汽机!D23"的字符串中获取行号，即23，如果pos_string是数据，则只读取数组中的第一个单元
     */
    if (!pos_string) {
        return null;
    }
    if (pd.type(pos_string) === "Array") {
        pos_string = pos_string[0];
    }
    if (pd.type(pos_string) !== "String") {
        pos_string = pos_string.toString();
    }
    pos_string = pos_string.trim();
    if (!pos_string) {
        return null;
    }
    let _sb_ = pos_string.split("!");
    let sub_pos = _sb_[_sb_.length - 1] // 取_sb_列表的最后一项
    _sb_ = sub_pos.match(/\d+/g)[0]; // 取匹配到的第一个数字字符串
    if (_sb_) {
        return parseInt(_sb_);
    } else {
        return null;
    }
}

export function get_col_from_pos_string(pos_string) {
    if (!pos_string) {
        return null;
    }
    if (pd.type(pos_string) === "Array") {
        pos_string = pos_string[0];
    }
    if (pd.type(pos_string) !== "String") {
        pos_string = pos_string.toString();
    }
    pos_string = pos_string.trim();
    if (!pos_string) {
        return null;
    }
    let _sb_ = pos_string.split("!");
    let sub_pos = _sb_[_sb_.length - 1] // 取_sb_列表的最后一项
    _sb_ = sub_pos.match(/[A-Za-z]+/g)[0]; // 取匹配到的第一个数字字符串
    if (_sb_) {
        return _sb_.trim();
    } else {
        return null;
    }
}

export function get_test_point_name(pos_string) {
    /**
     * 获取pos_string字符串对应的测点的名称
     */
    let row = get_row_from_pos_string(pos_string);
    if (pos_string.startsWith("原始数据")) {
        return get_cell_value(row, 1, "原始数据")
    } else if (pos_string.startsWith("数据整理")) {
        return get_cell_value(row, 2, "数据整理")
    }
}

export function update_project(item_k) {
    /**
     * 更新项目信息，保存
     */
    create_new_sheet("pro_dat"); // 确保项目信息表存在
    let dat_str = get_cell_value(1, 1, "pro_dat");
    let data;
    let name = item_k["name"];
    if (dat_str) {
        // 存在项目信息字符串
        data = JSON.parse(dat_str);
        if (data[name]) {
            // 如果已经存在该测点，则更新该测点的io属性
            data[name]["io"] = item_k["io"];
        } else {
            // 如果不存在该测点，则创建该测点并设置其io属性
            data[name] = {};
            data[name]["io"] = item_k["io"];
        }
    } else {
        // 如果不存在项目信息
        data = {};
        data[name] = {};
        data[name]["io"] = item_k["io"];
    }
    let proj_str = JSON.stringify(data);
    set_cell_value(1, 1, proj_str, "pro_dat");
}

function activate_sheet(sheet_name) {
    /**
     * 激活当前工作簿中的某个单元格
     * @type {string}
     */
    let sheet = get_sheet_by_name(sheet_name);
    sheet.Activate();
}

function getLastRowIndexOfSheet(sheet_name) {
    /**
     * 获取指定工作表中数据区域的最后一行的行号。如果不传入sheet_name则默认取当前活动的sheet。该方法不会导致活动sheet改变，即可以取未激活的
     * sheet的数据区域的行号。
     */
    let sheet;
    if (sheet_name === undefined) {
        sheet = app.ActiveSheet;
    } else {
        let sheets = app.Worksheets;
        sheet = sheets.Item(sheet_name);
    }
    if (sheet) {
        let usedRange = sheet.UsedRange;
        // Application.Range("B4", sheet.Range("B4").End(wps.Enum.xlDown)).Select();
        return usedRange.Rows.Count;
    } else {
        console.log("不存在名为" + sheet_name + "的sheet，请检查！");
        return 0;
    }
}

export function getLastColumnIndexOfSheet(sheet_name) {
    /**
     * 获取指定工作表中已拥有数据的最后一行的列号
     */
    let active_sheet = app.ActiveSheet;
    let sheet;
    if (sheet_name === undefined) {
        sheet = app.ActiveSheet;
    } else {
        let sheets = app.Worksheets;
        sheet = sheets.Item(sheet_name);
    }
    if (sheet) {
        sheet.Activate()
        let usedRange = sheet.UsedRange;
        // Application.Range("B4", sheet.Range("B4").End(wps.Enum.xlDown)).Select();
        let col_count = usedRange.Columns.Count;
        let real_cols = col_count;
        for (let i = col_count; i > 3; i--) {
            if (get_cell_value(1, i).trim() === "") {
                real_cols = i - 1;
            } else {
                break;
            }
        }
        active_sheet.Activate()
        return real_cols;
    } else {
        console.log("不存在名为" + sheet_name + "的sheet，请检查！");
        active_sheet.Activate()
        return 0;
    }

}

function extend_list_by_alternative_symbols(origin_list, alter_symbol) {
    /**
     * 将列表中的可替换字符串更换形式，从而形成更全面的字符串
     *
     *     例如：
     *     origin=["1号高加进汽压力"]
     *     origin=extend_list_by_alternative_symbols(origin, {"1": "一", "汽": "气"})
     *     则结果为：
     *     origin=['1号高加进汽压力', '一号高加进汽压力', '1号高加进气压力', '一号高加进气压力']
     */
    if (!alter_symbol) {
        alter_symbol = eu.alternative_symbol;
    }
    if (typeof origin_list === "string") {
        origin_list = [origin_list];
    }
    for (let idx in origin_list) {
        let item = origin_list[idx];
        for (let k in alter_symbol) {
            let v = alter_symbol[k];
            let new_item = item.replace(k, v);
            if (!lodash.includes(origin_list, new_item)) {
                origin_list.push(new_item);
            }
            new_item = item.replace(v, k);
            if (!lodash.includes(origin_list, new_item)) {
                origin_list.push(new_item);
            }
        }
    }
    return origin_list;
}

function get_standard_point(name_list, sp = eu.StandardPoint) {
    /**
     *  获取给定的name_list属于哪个测点，该测点只指定了位置，不指定具体的参数，如高压主蒸汽，但是压力还是温度不确定
     *  例如:
     *  get_standard_point(["高压主汽压力", "高压主气压力"])， 返回["main_steam", "P"]
     */

        // --------------------------- 先判断name_list属于StandardPoint中哪一个测点下 --------------------------------
    let matched_pos = null;
    for (let k in sp) {
        let v = sp[k];
        let include = v["include"];
        let exclude = v["exclude"];
        if (!include) { // 如果include为空，则进行下一次循环
            continue
        }

        for (let cur_name of name_list) { // 遍历用户传入的测点名
            for (let instr of include) {
                if (lodash.isString(instr)) { // 如果include中的元素是字符串
                    if (lodash.includes(cur_name, instr)) { // 如果用户传入的测点名字符串中包含StandardPoint中指定的include名，则处理该测点
                        let flag = true;
                        if (exclude) {
                            for (let ex_str of exclude) {
                                if (lodash.includes(cur_name, ex_str)) {
                                    flag = false;
                                    break;
                                }
                            }
                        }
                        if (flag) { // 如果测点名没有被exclude排除掉，则继续
                            matched_pos = k; // 如果找到了测点，则退出循环
                            break;
                        }
                    } else {
                        // 如果未找到测点，则继续判断name_list中的下一个同义词测点名是否匹配当前测点位置
                    }
                } else if (lodash.isArray(instr)) { // 如果include中的元素是列表
                    let matched = true;
                    for (let _instr of instr) {
                        if (!lodash.includes(cur_name, _instr)) {
                            matched = false;
                            break;
                        }
                    }
                    if (matched) {
                        let flag = true;
                        if (exclude) {
                            for (let ex_str of exclude) {
                                if (lodash.includes(cur_name, ex_str)) {
                                    flag = false;
                                    break;
                                }
                            }
                        }
                        if (flag) {
                            matched_pos = k
                            break;
                        }
                    }
                }
            }
            if (matched_pos !== null) {
                break;
            }
        }
        if (matched_pos !== null) {
            break;
        }
    }
    // --------------------------- 先判断name_list属于StandardPoint中哪一个测点下 --------------------------------
    if (matched_pos === null) {
        return null;
    } else if (matched_pos in ["regulator_station", "fgh_gas_in", "fgh_gas_out", "fgh_water_in", "fgh_water_out",
        "generator_gas_turbine", "hrsg_flue_out", "feed_water_mid", "feed_water_low"]) {
        wps.PluginStorage.setItem("group_type", "联合循环1")
    }
    // --------------------------- 匹配具体的值类型 --------------------------------------------
    let _name = name_list[0];
    let para;
    if (lodash.includes(_name, "压力") || lodash.includes(_name, "背压")) {
        para = "P";
    } else if (lodash.includes(_name, "温度")) {
        para = "T";
    } else if (lodash.includes(_name, "差压") && !lodash.includes(_name, "差压计算")) { // 差压计算的一般是流量
        para = "dp";
    } else if (lodash.includes(_name, "流量")) {
        para = "F";
    } else if (lodash.includes(_name, "焓值")) {
        para = "H";
    } else if (lodash.includes(_name, "熵值")) {
        para = "S";
    } else if (lodash.includes(_name, "功率因数")) {
        para = "factor";
    } else if (lodash.includes(_name, "功率")) {
        para = "power";
    } else if (lodash.includes(_name, "频率")) {
        para = "freq";
    } else if (lodash.includes(_name, "电压")) {
        para = "U";
    } else if (lodash.includes(_name, "电流")) {
        para = "I";
    } else if (lodash.includes(_name, "面积")) {
        para = "area";
    } else if (lodash.includes(_name, "效率")) {
        para = "eta";
    } else if ( // 水位测点单独处理
        matched_pos === "condenser_water_lvl" || matched_pos === "deaerator_water_lvl") {
        if (lodash.includes(_name, "开始")) {
            para = "start";
        } else if (lodash.includes(_name, "结束")) {
            para = "end";
        }
    } else if (matched_pos === "datetime") {  // 日期时间测点单独处理
        if (lodash.includes(_name, "日期")) {
            para = "start_date";
        } else if (lodash.includes(_name, "开始")) {
            para = "start";
        } else if (lodash.includes(_name, "结束")) {
            para = "end";
        }
    } else if (matched_pos === "leakage_known") {
        para = "F";
    } else if (matched_pos === "leakage_unknown") {  // 不明漏量测点单独处理
        if (lodash.includes(_name, "占比") || lodash.includes(_name, "百分比")) {
            if (lodash.includes(_name, "炉侧") || lodash.includes(_name, "锅炉")) {
                para = "percentage_boiler";
            } else {
                para = "percentage_turbine";
            }
        }
    }
    // --------------------------- 匹配具体的值类型 --------------------------------------------

    return [matched_pos, para];
}

function dispatch_point_to_para(var_name, sp = eu.StandardPoint) {
    /**
     * 获取var_name所属的测点名，例如:
     * dispatch_point_to_para("高压主蒸汽压力")，返回["main_steam", "P"]
     */
        // 首先获得用户输入测点名的可能变化，如：1号高加=一号高加
    let name_list = extend_list_by_alternative_symbols(var_name);
    // 获取对应的标准测点名
    return get_standard_point(name_list, sp);
}

function dispatch_point(var_name, i, sheet_name, sp = eu.StandardPoint) {
    /**
     * 将用户自定义的变量参数归类到标准测点清单中，var_name为试验期间 用户输入的测点名，例如"高压主汽温度"。该方法操作后，每个找到的测点将会增加exists属性，用于
     * 记录机组中是否存在该标准测点。
     * sp是标准测点对象，只能是eu.StandardPoint或者eu.StandardPointOrigin
     例如：
     dispatch_point("高压蒸汽压力", 20, "数据整理")，该语句会将eu.StandardPoint中main_steam.P参数的href设置为数据整理表中的第20行，以后使用main_steam.P引用时，会自动引用到指定位置的数据
     * @param var_name 测点名成
     * @param i 测点所属的行号
     * @returns {Array}
     */
    // 首先获得用户输入测点名的可能变化，如：1号高加=一号高加
    var_name = var_name.split("_")[0]  // 只有下划线前的字符串用来判断测点位置，后面的字符串是表号和单位，弃掉，否则会影响A、B侧之类测点的判断
    var_name = var_name.split("(")[0]  // 括号里的字符串为备注信息，也不参与测点信息判断
    let name_list = extend_list_by_alternative_symbols(var_name);
    // 获取对应的标准测点名
    let _sd = get_standard_point(name_list);
    if (_sd) {
        if (sheet_name === "数据整理") {
            let bak_info = get_cell_value_by_pos(`数据整理!A${i}`)
            if (bak_info === "参考" || lodash.includes(bak_info, "不准")) {
                return
            }
        }
        let standard_point = _sd[0];
        let para = _sd[1];
        if (!sp[standard_point]["href"]) {
            // 如果当前测点没有href属性，就创建
            sp[standard_point]["href"] = {};
        }
        // 当前测点有href属性，但不存在对应的para参数时，创建对应参数数据
        let para_s = sp[standard_point]["href"][para];
        if (!para_s || !para_s.length > 0) {
            // 如果para_s为空值，或其长度不大于0，则初始化赋值
            sp[standard_point]["href"][para] = [sheet_name + "!{Y}" + i,];
            sp[standard_point]["exists"] = true;
            if (standard_point.search("heater") !== -1) {
                for (let i of ["1", "2", "3", "pre_3", "4", "5", "6", "7", "8", "9"]) {
                    let pos_name = ["heater_" + i + "_vapor_in", "heater_" + i + "_vapor_out", "heater_" + i + "_water_in", "heater_" + i + "_water_out",];
                    if (pos_name.indexOf(standard_point) !== -1) { // 如果发现任一个加热器的测点，则加热器存在
                        sp["heater_" + i]["exists"] = true;
                        break;
                    }
                }
            }

        } else {
            //如果存在对应的参数，且当前参数与现有参数不同，则添加
            if (!lodash.includes(para_s, sheet_name + "!{Y}" + i)) sp[standard_point]["href"][para].push(sheet_name + "!{Y}" + i);
        }
    }

}

function refreshTestPoints(in_deep) {
    /**
     * 该方法扫描”数据整理“工作表中第2列变量名，并根据参数名将excel中的引用位置链接到enum.StandardTestPoint中，以方便后续计算时直接引用测点
     * 在后续计算时则可以使用 main_steam.P 等方式引用测点的数据所在的位置。
     * @param {boolean} 是否需要递归调用以确保初始化扫描测点
     * @type {string}
     */
        // 查找“数据整理”工作表，查询对应的参数位置
        // let sheet = get_sheet_by_name("数据整理");
        // let need_refreshed = wps.PluginStorage.getItem("need_refreshed");
    let need_refreshed = true;
    if (need_refreshed === null || need_refreshed === true) {  // 如果测点需要扫描
        let rows = getLastRowIndexOfSheet("数据整理");
        for (let i = 2; i < rows; i++) {
            // 遍历每一行的测点名，前四行分别是”项目“，”开始日期“，”开始时间“，”结束时间“，因此从第5行开始
            let user_defined_name = get_cell_value(i, 2, "数据整理");
            if (user_defined_name) {
                dispatch_point(user_defined_name, i, "数据整理", eu.StandardPoint);
                if (lodash.includes(user_defined_name, "取压方式")) { // 数据整理下方的流量原件计算区域不扫描
                    break;
                }
            } // 将当前测点分配到标准测点清单中
        }
        wps.PluginStorage.setItem("need_refreshed", false)
    }
    let sp = eu.StandardPoint
    if (sp["main_steam"]["exists"] || sp["outlet_low"]["exists"]) {
        console.log("标准测点：");
        console.log(eu.StandardPoint);
    } else {
        if (in_deep === undefined || in_deep === true) { // 是否需要递归
            wps.PluginStorage.setItem("need_refreshed", true);
            refreshTestPoints(false);
        }
    }
}

function refreshOriginPoints() {
    /**
     * 该方法扫描”数据整理“工作表中第2列变量名，并根据参数名将excel中的引用位置链接到enum.StandardTestPoint中，以方便后续计算时直接引用测点
     * 在后续计算时则可以使用 main_steam.P 等方式引用测点的数据所在的位置。
     * @param {boolean} 是否需要递归调用以确保初始化扫描测点
     * @type {string}
     */
        // 查找“原始数据”工作表，查询对应的参数位置
    let rows = getLastRowIndexOfSheet("原始数据");
    for (let i = 2; i < rows; i++) {
        // 遍历每一行的测点名，前四行分别是”项目“，”开始日期“，”开始时间“，”结束时间“，因此从第5行开始
        let user_defined_name = get_cell_value(i, 1, "原始数据");
        if (user_defined_name) {
            // console.log("user_defined_name=" + user_defined_name)
            dispatch_point(user_defined_name, i, "原始数据", eu.StandardPointOrigin);
        } // 将当前测点分配到标准测点清单中
    }
    console.log("标准测点：");
    console.log(eu.StandardPointOrigin);
    return eu.StandardPointOrigin;
}


export function read_sheet(sheet_name, index_col = null, as_df = true) {
    /**
     * 读入工作表，并以第index_col列为行标题，如果需要读入的是dataframe类型的数据对象，则需要传入index_col作为dataframe的行标题所在列。
     *
     * @type {any}
     */
    let sheet = get_sheet_by_name(sheet_name);
    if (!sheet) {
        return null;
    }
    if (as_df) {
        let df = new pd.DataFrame(sheet.UsedRange.Value2);
        df.set_axis(0, 1) // 默认第0行是列标题
        if (pd.type(index_col) === "Number") { // 以第一列为行标题
            df.set_index(index_col)
            return df;
        } else {
            return df;
        }
    } else {
        return sheet.UsedRange.Value2;
    }
}

export function set_position_of_para(var_name, pos_str, sp = eu.StandardPoint) {
    /**
     * 记录某个参数在excel表格中的位置，如果该参数的watch属性有值，则处理watch属性的引用。
     * 例如：set_position_of_para("main_steam.F", "汽机!{Y}32"}
     */
    var_name = var_name.replace("{", "").replace("}", "");
    let point_name = var_name.split(".")[0];
    let para = var_name.split(".")[1];
    if (!sp[point_name]["href"]) {
        sp[point_name]["href"] = {}
    }
    let pos = sp[point_name]["href"][para];
    if (pos && pos.length > 0) {
        // 如果需要设置的点已经存在且包含当前设置的pos_str，则不进行操作
        if (lodash.includes(pos, pos_str)) {
            //
            return true;
        } else {
            sp[point_name]["href"][para].push(pos_str);
        }
    } else {
        // 否则，创建当前点的引用位置列表
        sp[point_name]["href"][para] = [pos_str];
    }

    // 处理监视当前测点更新的引用
    let watch = sp[point_name].watch;
    if (!watch) {
        return true;
    }
    watch = watch[para];
    if (watch && pd.type(watch) === "Array" && watch.length > 0) {
        let link_row;
        let current_pos;
        for (let i = 0; i < watch.length; i++) {
            link_row = watch[i].split("{Y}")[1];
            let value = (get_cell_value(link_row, "D"));
            if (!lodash.includes(value, "watching")) {  // 说明已经更新过相关参数，则无须再次更新
                break;
            }
            for (let j = 4; j <= getLastColumnIndexOfSheet(); j++) {
                current_pos = pos_str.replace("{Y}", Num2ColumnChar(j));
                set_cell_formula(link_row, j, "=" + current_pos);
            }
        }
    }
    return true;
}

export function get_position_of_para(var_name, y, used_in,
                                     sp = eu.StandardPoint,
                                     fetch_all = false) {
    /**
     * 获取参数的引用位置。
     *
     * 可以通过"{point.para}.(var_name:row-i)的方式向该方法内传入参数，当point.para不存在时，调用其他方法进
     * 行point.para的获取，小括号中的参数为传递给其他方法的参数。此时，其他方法可以返回一个公式，以"formula:"开头。
     *
     * @param var_name: 变量名，程序内部使用的固定的变量名，如main_steam.P表示主蒸汽压力
     * @param y: 工况所在的列的数字索引
     * @param used_in: 该引用将应用的位置，该参数用于对参数位置字符串做进一步处理，如本表引用，则删除表名的字段，本表引用为true,否则为false
     * @param fetch_all: 是否需要返回所有位置的链接
     * @type {string}  返回字符串形如“数据整理!A1”，如果未查找到，返回null
     */
    try {
        if (typeof y !== "string") {
            y = Num2ColumnChar(y);
        }
        var_name = var_name.replace("{", "").replace("}", "");
        let point_name = var_name.split(".")[0];
        let para = var_name.split(".")[1];
        if (!sp[point_name]["href"]) {  // 如果sp[point_name]["href"]为空，则初始化为空字典
            sp[point_name]["href"] = {}
        }
        let pos_str = sp[point_name]["href"][para];
        if (!pos_str || !String(pos_str)) { // 如果pos_str为空，则继续转换
            // pos_str可能为多种类型的空值，如空字典，undefined等
            // pos_str可能返回空列表，即[]。
            let link = sp[point_name].link; // 查找当前点的替代点，如一号高加出水就是二号高加进水
            if (link && link.length > 0) {
                for (let i in link) {
                    let alter_id = link[i];
                    pos_str = sp[alter_id]["href"][para];
                    if (pos_str && pos_str.length > 0) {
                        console.log("未找到" + var_name + "对应的参数！但发现替代点" + alter_id + "." + para);
                        sp[point_name]["href"][para] = pos_str;
                        if (fetch_all) {
                            let res = []
                            for (let _pos of pos_str) {
                                _pos = _pos[0].replace("{Y}", y);
                                _pos = _pos.replace(used_in + "!", ""); // 当前表引用当前表，不需要表名
                                res.push(_pos)
                            }
                            return res
                        } else {
                            pos_str = pos_str[0].replace("{Y}", y);
                            pos_str = pos_str.replace(used_in + "!", ""); // 当前表引用当前表，不需要表名
                            return pos_str;
                        }
                    }
                }
            }
            if (point_name === "leakage") {
                let obj = sp.leakage.obj;
                let vars = var_name.split(para)[1];
                vars = vars.substr(2, vars.length - 3);
                let _sd = vars.split(":");
                let idx_row;
                if (_sd[0] === "main_steam.F") {
                    idx_row = _sd[1].replace("{Y}", y)
                }
                let leakage = obj.get_leakage(para, idx_row);
                if (!leakage) {
                    return null;
                }
                leakage = "formula:" + leakage;
                return leakage;
            }
            console.log("未找到" + var_name + "对应的参数！");
            return null;
        } else {
            if (pos_str.length === 1) {  // 当只有一个链接时，fetch_all为true还是false都没有意义
                fetch_all = false;
            }
            if (fetch_all) {
                let res = [];
                for (let pos_ of pos_str) {
                    pos_ = pos_.replace("{Y}", y);
                    if (used_in) {
                        // 如果used_in不为空，则进行以下操作
                        pos_ = pos_.replace(used_in + "!", ""); // 当前表引用当前表，不需要表名
                    }
                    res.push(pos_)
                }
                return res;
            } else {
                pos_str = pos_str[0].replace("{Y}", y);
                if (used_in) {
                    // 如果used_in不为空，则进行以下操作
                    pos_str = pos_str.replace(used_in + "!", ""); // 当前表引用当前表，不需要表名
                }
                return pos_str;
            }
        }
    } catch (e) {
        console.log("获取参数引用位置时发生错误，此时引用的点名为：" + var_name + "，当前的StandardPoint对象如下：");
        console.log(sp);
    }
}

export function get_value_of_point(para_str, y) {
    /**
     * 获取点名对应的值，如main_steam.P返回主蒸汽压力的值
     * @private
     */
    let pos_str = get_position_of_para(para_str, y, "");
    if (!pos_str) {
        console.log("未找到" + para_str + "，请确认！");
        return null;
    }
    let _sd = pos_str.split("!");
    let sheet_name = _sd[0];
    let x = parseInt(_sd[1].match(/\d+/)[0]);
    return get_cell_value(x, y, sheet_name);
}

export function form_formula(func_str, paras_list) {
    if (!func_str.startsWith("=")) {
        func_str = "=" + func_str;
    }
    if (typeof paras_list === "string") {
        return func_str + "(" + paras_list + ")";
    } else {
        let paras_str = paras_list.join(",");
        return func_str + "(" + paras_str + ")";
    }
}

export function form_formula1(func_str, y) {
    /**
     * 替换{main_steam.F}之类的参数
     */
    if (!func_str.startsWith("=")) {
        func_str = "=" + func_str;
    }
    if (typeof y !== "string") {
        y = Num2ColumnChar(y);
    }
    let sheet_name = app.ActiveSheet.Name;
    let var_name_list = new Set(func_str.match(/{([^}]+)}/g));
    for (let item of var_name_list) {
        let dest = get_position_of_para(item.substr(1, item.length - 2), y, sheet_name);
        func_str = func_str.replaceAll(item, dest);
    }
    return func_str;
}

export function form_formula2(func_str, y, row) {
    /**
     * 替换{row-1}之类的参数
     */
    if (!func_str.startsWith("=")) {
        func_str = "=" + func_str;
    }
    if (typeof y !== "string") {
        y = Num2ColumnChar(y);
    }
    let var_name_list = new Set(func_str.match(/{([^}]+)}/g));
    let dest;
    for (let item of var_name_list) {
        dest = y + (row + parseInt(item.split("+")[1]));
        if (dest.endsWith("NaN")) {
            // 说明表达式中没有+号
            dest = y + (row - parseInt(item.split("-")[1]));
            if (dest.endsWith("NaN")) {
                // 说明表达式中没有-号
                dest = y + row;
            }
        }

        func_str = func_str.replaceAll(item, dest);
    }
    return func_str;
}

function watch_var(var_name, pos) {
    /**
     * 添加监视，当var_name所代表的参数有了计算结果后，会自动将pos对应的excel单元格的数值指向var_name所在的位置
     * @param var_name: 形如"{main_steam.F}"这样的变量名
     * @param pos: 形如"汽机!{Y}23"这样的Excel单元索引
     */
    if (!var_name) {
        console.log("systemdemo.js -> watch_var()监视的测点不存在：" + var_name)
        return null;
    }
    let sp = eu.StandardPoint;
    let point = sp[var_name.split(".")[0]];
    let para = var_name.split(".")[1];
    if (!point["watch"]) { // 如果当前测点没有watch属性，就添加
        point["watch"] = {}
    }
    let watch = point.watch[para]; // 如果当前测点的watch属性
    if (!watch) {
        point.watch[para] = [pos]
    } else {
        if (pd.type(watch) === "Array") {
            if (!lodash.includes(watch, pos)) {
                point.watch[para].push(pos)
            }
        }
    }
}

export function add_delete_flag(row, sheet_name) {
    /**
     *  给指定行添加删除标记，后续会删除该行，两种调用方式
     *  add_delete_flag("汽机!{Y}21")
     *  add_delete_flag(12, "汽机")
     */
    let sheet;
    if (sheet_name) {
        sheet = sheet_name;
    }
    try {
        let _sd = row.split("!{Y}");
        sheet = _sd[0];
        row = _sd[1];
        set_cell_value(parseInt(r_idx), 2, "即将删除", sheet);
    } catch (e) {
        // console.log(e)
        set_cell_value(parseInt(row), 2, "即将删除", sheet_name)
    }

}

export function delete_row_by_flag(rows, sheet_name) {
    /**
     * 根据rows中的行是否带删除标记，按需删除相应行
     */
    let row_idx_list = []
    for (let row of rows) {
        if (get_cell_value(row, 2, sheet_name) === "即将删除") {
            row_idx_list.push(parseInt(row))
        }
    }
    delete_rows_of_sheet(row_idx_list, sheet_name)
}

export function delete_rows_of_sheet(row_idx_arr, sheet_name) {
    /**
     * 删除表格行，其余行上移
     */
    let rows = row_idx_arr.sort().reverse();
    for (let row of rows) {
        delete_row(row, sheet_name)
    }
}

function extract_from(string = "高压缸", para = "F", y) {
    /**
     * 查找所有从"高压缸"抽汽的抽汽点的”F"之和，返回的是索引
     */
    let formula = "";
    let result;
    for (let j = 0; j < 10; j++) {  // 凡是从高压缸抽走的蒸汽流量都要扣掉
        let from;
        let io = eu.StandardPoint["extract_" + j].io;
        if (io) {
            from = io["from"]
            if (from === string) {
                formula = formula + "+{extract_" + j + "." + para + "}";
            }
        }
    }
    if (formula !== "") {
        result = form_formula1(formula, y);
        if (result.startsWith("=")) { // 删除form_formula1返回公式的等号
            result = result.substr(1);
        }
        result = result.trim();
        if (result.startsWith("+")) { // 删除表达式最前面的“+”号
            result = result.substr(1);
        }
        if (lodash.includes(result, "+")) { // 如果表达式是多个元素之和，则需要使用小括号括起来
            result = "(" + result + ")";
        }
    } else {
        result = 0;
    }
    return result;
}

export function fill_cell(row, col, flag, expression, action = "delete", sp = eu.StandardPoint) {
    /**
     * 填充指定单元格，默认填充当前激活的工作簿
     *
     * flag === "para" 表示单元格是单个测点的引用，如expression = "main_steam.P"
     * flag === "formula" 表示单元格是公式，如expression = "{main_steam.H}=H_PT({row-2}, {row-1})"，其中{row}是传入的第一个参数row
     *
     * formula表达式中的大括号表示具有特殊意义的变量，目前有三类：
     * 1. {main_steam.H}表示{测点.变量}，代表某个测点上的变量名
     * 2. {row-1}表示相对当前行的上下某一行，如-1表示上一行，-2表示上两行，+1表示下一行等
     * 3. {extract(from='高压缸').F}，表示所有从'高压缸'抽汽的抽汽测点的流量之和
     *
     * action="delete"且flag="formula"时，公式只能为简单的加减运算，如果公式中存在不可解析的参数，则会删除该参数的引用，例如公式为
     * "{leakage.高压门杆漏气总量}={leakage.高压门杆一漏}+{leakage.高压门杆二漏}"
     * 当leakage.高压门杆二漏不存在时，公式会变为"{leakage.高压门杆漏气总量}={leakage.高压门杆一漏}"
     * 当公式右侧所有的引用都不存在时，则该行公式会被打上删除标记，在后续处理中，该行会被删除。
     * 如果action="delete"，且flag="para"，则当引用的参数不存在时，该行会被标记删除
     *
     * action="watch"（只有当flag="para"时，action才可以取该值）表示监测目标参数，当目标参数有值时，再填充该行，例如当前单元格引用
     * 了“main_steam.F"时，如果main_steam.F不存在，常规引用会报错。如果设置action="watch"，后续计算出了main_steam.F时，会回过
     * 头来再处理该行，将计算出的值的引用位置填入该行，相当于先用后计算。
     *
     * action取其他值时，如果引用参数不存在，则默认取值为action
     *
     * 当flag="para"时，可以通过"{point.para}.(var_name:row-i)的方式向该方法内传入参数，当point.para不存在时，调用其他方法进
     * 行point.para的获取，小括号中的参数为传递给其他方法的参数。此时，其他方法可以返回一个公式，以"formula:"开头，公式主体将作为参数
     * 引用填入指定的单元格。
     *
     * @param row: 填充单元的行号，如果为null，则取当前激活的行
     * @param col: 填充单元格的列号，如果为null，则取当前激活的列
     * @param flag: "para"表示参数，"formula"表示公式
     * @param expression: 表示参数或公式的字符串
     * @param action: 当参数或公式中引用了不可解析的参数时，执行的操作
     */
    if (row === null) {
        row = app.ActiveCell.Row
    }
    if (col === null) {
        col = app.ActiveCell.Column
    }
    let y_char = Num2ColumnChar(col);
    if (flag === "para") {
        // 表示该单元是单个测点的引用
        let pos = get_position_of_para(expression, y_char, null, sp);
        if (pos) {
            if (pos.startsWith("formula:")) {
                // 说明返回的位置信息是一个公式，漏气信息一般会返回公式，虽然想获取参数，但是参数不存在时，调用其他方法返回公式
                pos = pos.replace("formula:", "");
                app.Cells.Item(row, col).Formula = pos;
            } else {
                // if (!expression.startsWith("{")) {
                //     expression = "{" + expression + "}";
                // }
                // app.Cells.Item(row, col).Formula = form_formula1(expression, y_char);
                app.Cells.Item(row, col).Formula = "=" + pos;
            }
        } else {
            if (action === "watch") { // 如果指定了替代的默认值，则使用该值
                // 填充该cell时，对应的参数可能还不存在值，将该行加入检测参数的触发行中，当监测参数有值后，会自动回过头来填充该行
                app.Cells.Item(row, col).Value2 = "watching " + expression
                watch_var(expression, app.ActiveSheet.Name + "!{Y}" + row)
            } else if (action === "delete") {
                add_delete_flag(app.ActiveSheet.Name + "!{Y}" + row)
                return true  // 如果要删除，就不需要设置该行格式了，直接返回
            } else {
                app.Cells.Item(row, col).Value2 = action;
                set_position_of_para(expression, app.ActiveSheet.Name + "!{Y}" + row); // 如果是公式，则默认存储公式对应的参数测点
            }
        }
    } else if (flag === "formula") {
        let _split_ = split(expression, "=", 2)
        let para = _split_[0].replace("{", "").replace("}", "").trim();
        let formula = _split_[1];
        let sheet_name = app.ActiveSheet.Name;
        let var_name_list = new Set(formula.match(/{([^}]+)}/g));
        let dest;
        for (let item of var_name_list) {
            if (lodash.includes(item, "row")) { // 处理形如{row+1}的变量
                dest = y_char + (row + parseInt(item.split("+")[1]));
                if (dest.endsWith("NaN")) {
                    // 说明表达式中没有+号
                    dest = y_char + (row - parseInt(item.split("-")[1]));
                    if (dest.endsWith("NaN")) {
                        // 说明表达式中没有-号
                        dest = y_char + row;
                    }
                }
                formula = formula.replaceAll(item, dest);
            } else if (lodash.includes(item, "(")) {  // 处理形如 {extract_from("高压缸").F}的变量
                item = item.replace("{", "").replace("}", "");
                let _sd_ = item.split(".")
                let exp = _sd_[0].substr(0, _sd_[0].length - 1) + ",'" + _sd_[1] + "','" + y_char + "')";
                let ex_ = eval(exp);
                if (ex_ === 0) {
                    formula = formula.replace(item, "0");
                } else {
                    formula = formula.replace("{" + item + "}", ex_);
                }
            } else { // 处理形如{main_steam.F}的变量
                dest = get_position_of_para(item.substr(1, item.length - 2), col, sheet_name);
                if (dest) {
                    formula = formula.replaceAll(item, dest);
                } else {
                    if (formula.startsWith(item)) {
                        formula = formula.replace(item, "");
                        if (formula.trim().startsWith("+")) {
                            formula = formula.trim().substr(1)  // 删除公式开头的+号
                        }
                    } else {
                        let idx = formula.indexOf(item);
                        let before_op = formula.substr(idx - 1, 1)
                        if (before_op === "+" || before_op === "-" || before_op === "*" || before_op === "/") {
                            formula = formula.replace(formula.substr(idx, item.length + 1), "")
                        } else {
                            formula = formula.replace(item, "")
                        }
                    }
                }
            }
        }
        formula = formula.trim()
        if (!formula) {  // 如果formula为空
            add_delete_flag(app.ActiveSheet.Name + "!{Y}" + row);
            return true;
        }
        if (!formula.startsWith("=")) {
            formula = "=" + formula;
        }
        app.Cells.Item(row, col).Formula = formula;
        if (para !== "") { // 如果有参数名，则存储公式对应的参数测点
            set_position_of_para(para, app.ActiveSheet.Name + "!{Y}" + row); // 如果是公式，则默认存储公式对应的参数测点
        }
    }

    // ----------------------- 处理数据格式 ------------------------------------
    let unit = get_cell_value(row, 3);
    if (lodash.includes(["MPa", "t/h", "kJ/(kgK)", "MW"], unit)) {
        app.Cells.Item(row, col).NumberFormat = "0.000";
    } else if (lodash.includes(["%", "m3", "h"], unit)) {
        app.Cells.Item(row, col).NumberFormat = "0.00";
    } else if (lodash.includes(["℃", "kJ/kg", "mm", 'kJ/(kW.h)'], unit)) {
        app.Cells.Item(row, col).NumberFormat = "0.0";
    } else if (lodash.includes(["y.m.d"], unit)) {
        app.Cells.Item(row, col).NumberFormat = "yyyy-m-d";
    } else if (lodash.includes(['hh:mm'], unit)) {
        app.Cells.Item(row, col).NumberFormat = "hh:mm";
    }
    activate_sheet(get_active_sheet_name())
    return true;
    // ======================== 处理数据格式 =====================================
}

export function showTaskPane(path = "demotaskpane", title = "任务栏", update_if_exist = false) {
    /**
     * @param path: 面板组件的url路径
     * @param title: 面板组件的标题
     * @param update_if_exist: 如果面板存在，是否更新，默认存在则隐藏面板，如果为true，则存在时会强制刷新面板
     * @type {string[]}
     */
        // 默认展示ID=taskpane_id对应的网页
        // 每次请求都重新刷新网页，以保证数据都是当前激活工作簿的数据
        //
    let name = path.split('/')
    let tsId = wps.PluginStorage.getItem(name); // 通过面板名获取面板的id
    if (!tsId) {
        let taskpane = wps.CreateTaskPane(Util.GetUrlPath() + path, title); // 没有则加载id对应的网页，这里的name对应vue路由
        let id = taskpane.ID;
        wps.PluginStorage.setItem(name, id); // 设置面板名对应的id值
        taskpane.Visible = true;
    } else {
        let taskPane = wps.GetTaskPane(tsId);
        if (taskPane.Visible) {
            taskPane.Visible = false;
            taskPane.Delete();
            wps.PluginStorage.setItem(name, null);
        }
        if (update_if_exist) {
            taskPane = wps.CreateTaskPane(Util.GetUrlPath() + path, title); // 没有则加载id对应的网页，这里的name对应vue路由
            let id = taskPane.ID;
            wps.PluginStorage.setItem(name, id); // 设置面板名对应的id值
            taskPane.Visible = true;
        }
    }
}

export default {
    openOfficeFileFromSystemDemo,
    InvokeFromSystemDemo,
    refreshTestPoints,
    refreshOriginPoints,
    RGB,
    createTurbineSheet,
    activate_sheet,
    getLastRowIndexOfSheet,
    getLastColumnIndexOfSheet,
    get_row_from_pos_string,
    app,
    get_active_sheet_name,
    get_sheet_by_name,
    get_cell_value,
    set_cell_value,
    create_new_sheet,
    update_project,
    self_debug,
    get_value_of_point,
    get_cell_value_by_pos,
    get_position_of_para,
    get_test_point_name,
    dispatch_point_to_para,
    fill_cell,
    read_sheet,
    delete_row_by_flag,
    showTaskPane,
    Num2ColumnChar,
    ColumnChar2Num,
};
